/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package elaborate.tag_analysis.client.actions.cossim;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * input a file contains tags and character vector in the following format:
 * ${tag} 0001111001111......
 * or
 * ${tag} 9,0,1,10,100......
 * or
 * "${tag name with space}" 0001111001111......
 * or
 * "${tag name with space}" 9,0,1,10,100......
 * each tag should occupy a line
 * @author lendle
 */
public class CosineSimilarityCalc {

    public void calculate(File srcFile, File targetFile) throws Exception {
        BufferedReader input=null;
        PrintWriter output=null;
        List<CosineSumTagEntry> list=new ArrayList<CosineSumTagEntry>();
        try{
            input=new BufferedReader(new InputStreamReader(new FileInputStream(srcFile), "utf-8"));
            output=new PrintWriter(new OutputStreamWriter(new FileOutputStream(targetFile), "utf-8" ));
            String str=input.readLine();
            while(str!=null){
                CosineSumTagEntry entry=this.parseEntry(str);
                list.add(entry);
                str=input.readLine();
            }
            outputHeader(output, list);
            for(int i=0; i<list.size(); i++){
                double [] result=this.calculateCosineSimilarity(list.get(i), list);
                this.outputRow(output, i, result, list);
            }
        }finally{
            input.close();
            output.close();
        }
    }

    private CosineSumTagEntry parseEntry(String line) throws Exception {
        String[] strs = null;
        if(line.contains("\"")){
            //the tag name is surrounded by double quote
            strs=new String[2];
            int index1=line.indexOf("\"");
            int index2=line.lastIndexOf("\"");
            strs[0]=line.substring(index1+1, index2).trim();
            strs[1]=line.substring(index2+1).trim();
        }
        else{
            //the tag name is not surrounded by double quote
            strs=line.split(" ");
        }
        if (strs == null || strs.length != 2) {
            throw new Exception("wrong format!");
        }
        CosineSumTagEntry entry = new CosineSumTagEntry();
        entry.setTagName(strs[0]);
        if (strs[1].contains(",")) {
            //tokenize by ,
            String[] strArray = strs[1].split(",");
            int[] intArray = new int[strArray.length];
            for (int i = 0; i < strArray.length; i++) {
                intArray[i] = Integer.valueOf(strArray[i]).intValue();
            }
            entry.setCharacteristicArray(intArray);
        } else {
            //treat each element as single digit
            int [] intArray=new int[strs[1].length()];
            for(int i=0; i<strs[1].length(); i++){
                intArray[i]=Integer.valueOf(""+strs[1].charAt(i)).intValue();
            }
            entry.setCharacteristicArray(intArray);
        }
        return entry;
    }
    
    public double calculateCosineSimilarity(CosineSumTagEntry entry1, CosineSumTagEntry entry2){
        BigDecimal vectorLength1=new BigDecimal("0");
        for(int i : entry1.getCharacteristicArray()){
            vectorLength1=vectorLength1.add(new BigDecimal(""+i).pow(2));
        }
        vectorLength1=new BigDecimal(""+Math.pow(vectorLength1.doubleValue(), 0.5));
        
        BigDecimal vectorLength2=new BigDecimal("0");
        for(int i : entry2.getCharacteristicArray()){
            vectorLength2=vectorLength2.add(new BigDecimal(""+i).pow(2));
        }
        vectorLength2=new BigDecimal(""+Math.pow(vectorLength2.doubleValue(), 0.5));
        
        BigDecimal innerProduct=new BigDecimal("0");
        for(int i=0; i<entry1.getCharacteristicArray().length; i++){
            innerProduct=innerProduct.add(new BigDecimal(""+entry1.getCharacteristicArray()[i]).multiply(new BigDecimal(""+entry2.getCharacteristicArray()[i])));
        }
        innerProduct=innerProduct.divide(vectorLength1.multiply(vectorLength2), 4, BigDecimal.ROUND_HALF_UP);
        return innerProduct.doubleValue();
    }
    /**
     * calculate similarities between a tag entry and all tag entries
     * @param entry1
     * @param entries
     * @return 
     */
    public double [] calculateCosineSimilarity(CosineSumTagEntry entry1, List<CosineSumTagEntry> entries){
        double [] ret=new double[entries.size()];
        int i=0;
        for(CosineSumTagEntry entry2 : entries){
            ret[i]=this.calculateCosineSimilarity(entry1, entry2);
            i++;
        }
        return ret;
    }
    
    private void outputHeader(PrintWriter writer, List<CosineSumTagEntry> list) {
        //output the header
        writer.print(" ,");
        boolean firstOne=true;
        for(CosineSumTagEntry tag : list){
            if(!firstOne){
                writer.print(",");
            }
            writer.print(tag.getTagName());
            firstOne=false;
        }
        writer.println();
    }
    
    private void outputRow(PrintWriter writer, int ithRow, double[] result, List<CosineSumTagEntry> list) {
        writer.print(list.get(ithRow).getTagName());
        writer.print(",");
        for (int j = 0; j < result.length; j++) {
            if (j != 0) {
                writer.print(",");
            }
            writer.print(result[j]);
        }
        writer.println();
    }
    
    public static void main(String [] args) throws Exception{
        //generate sample
        PrintWriter output=new PrintWriter(new FileWriter("smallSample.txt"));
        Random random=new Random();
        for(int i=0; i<200; i++){
            StringBuffer buffer=new StringBuffer();
            buffer.append("a").append(i).append(" ");
            for(int j=0; j<100; j++){
                buffer.append(random.nextInt(2));
            }
            output.println(buffer.toString());
        }
        output.close();
        /////////////////
        CosineSimilarityCalc calc=new CosineSimilarityCalc();
        try {
            calc.calculate(new File("smallSample.txt"), new File("smallSample.csv"));
        } catch (Exception ex) {
            Logger.getLogger(CosineSimilarityCalc.class.getName()).log(Level.SEVERE, null, ex);
        }
//        CosineSimilarityCalc calc=new CosineSimilarityCalc();
//        try {
//            calc.calculate(new File("C:\\Users\\lendle\\Desktop\\test.txt"), new File("C:\\Users\\lendle\\Desktop\\test.csv"));
//        } catch (Exception ex) {
//            Logger.getLogger(CosineSimilarityCalcFrame.class.getName()).log(Level.SEVERE, null, ex);
//        }
    }
}
